/*
 * AIEngine a new generation network intrusion detection system.
 *
 * Copyright (C) 2013-2023  Luis Campo Giralte
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA  02110-1301, USA.
 *
 * Written by Luis Campo Giralte <luis.camp0.2009@gmail.com>
 *
 */
#include "HTTPSession.h"
#include <boost/spirit/include/qi.hpp> // For parsing the 5 tuple
#if defined(PYTHON_BINDING)
#include <boost/python/stl_iterator.hpp>
#endif

namespace qi = boost::spirit::qi;

namespace aiengine {

/* LCOV_EXCL_START */

SystemPtr system = SystemPtr(new System());

void HTTPSession::start() {

	boost::system::error_code ec;
	boost::asio::ip::tcp::endpoint endpoint = socket_.remote_endpoint(ec);

	if (!ec) {
		// We cache the ip and src port to avoid calls to remote_endpoint that
		// can generate a exception.
		ipsrc_ = endpoint.address().to_string();
		portsrc_ = endpoint.port();

		AIINFO << "New TCP Connection with " << ipsrc_ << ":" << portsrc_;
	}
	do_read();
}

void HTTPSession::do_read() {

        // Make the request empty before reading,
        // otherwise the operation behavior is undefined.
        request = {};

        bhttp::async_read(socket_, buffer_, request,
        	std::bind(
                	&HTTPSession::handle_read,
                        shared_from_this(),
                        std::placeholders::_1,
                        std::placeholders::_2));
}

void HTTPSession::handle_read(boost::system::error_code ec, std::size_t bytes_transferred) {

        boost::ignore_unused(bytes_transferred);

        total_bytes_ += bytes_transferred;

        // This means the client closed the connection
        if (ec)
                return do_close();

        ++ total_requests_;
        request_time_ = std::time(nullptr);
        process_request(std::move(request));
}

void HTTPSession::handle_write( boost::system::error_code ec, std::size_t bytes_transferred, bool close) {

        boost::ignore_unused(bytes_transferred);

        total_bytes_ += bytes_transferred;
        ++ total_responses_;

        if (ec) {
                // std::cout << "Error on write:" << ec.message() << "\n";
                return;
        }

        if (close) {
                // This means we should close the connection, usually because
                // the response indicated the "Connection: close" semantic.
                return do_close();
        }

        // We're done with the response so delete it
        res_ = nullptr;

        // Read another request
        do_read();
}

void HTTPSession::do_close() {

	AIINFO << "Closing TCP Connection with " << ipsrc_ << ":" << portsrc_;
	socket_.close();
}

void HTTPSession::process_request(bhttp::request<bhttp::dynamic_body>&& req) {

        bhttp::response<bhttp::dynamic_body> response;
        response.version(req.version());
        response.keep_alive(req.keep_alive());
        response.set(bhttp::field::server, "AIEngine " VERSION);
        response.content_length(0);

	if (stack_ == nullptr) {
		std::string_view message = "No stack available";

		response.keep_alive(false);
		response.set(bhttp::field::content_type, "text/html");
		response.result(bhttp::status::service_unavailable);
		boost::beast::ostream(response.body()) << message;
		response.content_length(message.length());

		AIINFO << "[" << ipsrc_ << ":" << portsrc_ << "] " << req.method()
			<< " " << req.target() << " " << response[bhttp::field::content_type]
			<< " " << response.result_int();

		return send_response(std::move(response));
	}

	if (req.method() == bhttp::verb::get)
        	handle_get_message(response);
	else if (req.method() == bhttp::verb::post)
        	handle_post_message(response);
	else if (req.method() == bhttp::verb::put)
        	handle_put_message(response);
	else {
		response.result(bhttp::status::bad_request);
                response.set(bhttp::field::content_type, "text/plain");
                boost::beast::ostream(response.body())
                	<< "Invalid request-method '"
                        << std::string(request.method_string())
                        << "'";
        }

	AIINFO << "[" << ipsrc_ << ":" << portsrc_ << "] " << req.method()
		<< " " << req.target() << " " << response[bhttp::field::content_type]
		<< " " << response.result_int();

	return send_response(std::move(response));
}

void HTTPSession::handle_get_message(bhttp::response<bhttp::dynamic_body> &response) {

	if (check_operations(response, get_operations_) == false)
		show_uris(response);
}

void HTTPSession::handle_post_message(bhttp::response<bhttp::dynamic_body> &response) {

	if (check_operations(response, post_operations_) == false) {
		response.result(bhttp::status::bad_request);
		response.set(bhttp::field::content_type, "text/plain");
		boost::beast::ostream(response.body()) << "Invalid POST resource '" << request.target() << "'";
		response.content_length(response.payload_size());
	}
}

void HTTPSession::show_protocols_summary(bhttp::response<bhttp::dynamic_body> &response) {

	std::ostringstream out;

	response.set(bhttp::field::cache_control, "no-store");
        response.set(bhttp::field::content_type, "text/plain");

	if (boost::beast::string_view accept = request[bhttp::field::accept]; accept.length() > 0) {
        	if (std::size_t found = accept.find("application/json"); found != std::string::npos) {
                	Json j;
                        response.set(bhttp::field::content_type, "application/json");
			stack_->showProtocolSummary(j);
                        out << j;
                } else
			stack_->showProtocolSummary(out);
	} else
		stack_->showProtocolSummary(out);

	boost::beast::ostream(response.body()) << out.str();
	response.content_length(response.payload_size());
        response.result(bhttp::status::ok);
}

// The network flows uri looks like
// /v1/flows
// /v1/flows?limit=100
// /v1/flows?l7protocol=dns&limit=100
// /v1/flows?l7protocol=ssl

void HTTPSession::show_network_flows(bhttp::response<bhttp::dynamic_body> &response) {

	std::ostringstream out;
	std::string uri(request.target());
	bool jsonize = false;
	UserFlowOptions options;

        if (boost::beast::string_view accept = request[bhttp::field::accept]; accept.length() > 0)
                if (std::size_t found = accept.find("application/json"); found != std::string::npos)
			jsonize = true;

	uri.erase(0, HTTPUris::show_network_flows.length());

	// Check if the URI contains parameters
	if (uri.find("?") != std::string::npos) {
                std::map<std::string, std::string> parameters;
		// Make the parameters flexible for the user queries
		std::vector<std::string> src_port_parameter{"srcport", "portsrc"};
		std::vector<std::string> dst_port_parameter{"dstport", "portdst"};
		std::vector<std::string> src_ip_parameter{"srcip", "ipsrc"};
		std::vector<std::string> dst_ip_parameter{"dstip", "ipdst"};

                uri.erase(std::remove(uri.begin(), uri.end(), '?'), uri.end());

                parse_arguments(uri, parameters);

		for (auto &item: src_port_parameter)
			if (auto it = parameters.find(item); it != parameters.end())
				options.portsrc = std::atoi(it->second.c_str());

		for (auto &item: dst_port_parameter)
			if (auto it = parameters.find(item); it != parameters.end())
				options.portdst = std::atoi(it->second.c_str());

		for (auto &item: src_ip_parameter)
			if (auto it = parameters.find(item); it != parameters.end())
				options.ipsrc = it->second;

		for (auto &item: dst_ip_parameter)
			if (auto it = parameters.find(item); it != parameters.end())
				options.ipdst = it->second;

		if (auto it = parameters.find("protocol"); it != parameters.end())
			options.protocol = std::atoi(it->second.c_str());

		if (auto it = parameters.find("limit"); it != parameters.end())
			options.limit = std::atoi(it->second.c_str());

		if (auto it = parameters.find("l7protocol"); it != parameters.end())
			options.l7protocol_name = it->second;
	}

	// We dont want to send 1.000.000 flows to the user over HTTP
	// that will make the browser slow so we will limit the number
	// of show flows if has not been set explicitly by the user
	if (options.limit == std::numeric_limits<int>::max()) options.limit = 102400;

	if (jsonize) {
        	Json j;
                response.set(bhttp::field::content_type, "application/json");

		stack_->showFlows(j, options);

                out << j;
	} else {
        	response.set(bhttp::field::content_type, "text/plain");

		stack_->showFlows(out, options);
	}

	response.set(bhttp::field::cache_control, "no-store");
	boost::beast::ostream(response.body()) << out.str();
	response.content_length(response.payload_size());
        response.result(bhttp::status::ok);
}

// The protocol uri looks like
// /v1/protocol?name=ssl
// /v1/protocol?name=ssl&stats_level=1
// /v1/protocol?name=ssl&stats_level=5&limit=100
// /v1/protocol?name=http&map=host

void HTTPSession::show_protocol_summary(bhttp::response<bhttp::dynamic_body> &response) {

        std::ostringstream out;
        std::vector<std::string> parameters;
        std::string uri(request.target());
        int level = 1;
        std::string protocol = "";
	std::string map_name = "";
	int32_t limit = std::numeric_limits<int>::max();
        bool jsonize = false;

        if (boost::beast::string_view accept = request[bhttp::field::accept]; accept.length() > 0)
                if (std::size_t found = accept.find("application/json"); found != std::string::npos)
                        jsonize = true;

        uri.erase(0, HTTPUris::show_protocol.length());

        // Check if the URI contains parameters
        if (uri.find("?") != std::string::npos) {
                std::map<std::string, std::string> parameters;

                uri.erase(std::remove(uri.begin(), uri.end(), '?'), uri.end());

                parse_arguments(uri, parameters);

                if (auto it = parameters.find("stats_level"); it != parameters.end())
			level = std::atoi(it->second.c_str());

                if (auto it = parameters.find("map"); it != parameters.end())
			map_name = it->second;

                if (auto it = parameters.find("name"); it != parameters.end())
			protocol = it->second;

                if (auto it = parameters.find("limit"); it != parameters.end())
			limit = std::atoi(it->second.c_str());
	}

	if (protocol.length() > 0) { // The user setup the parameter name
		if (jsonize) {
			Json j;
			response.set(bhttp::field::content_type, "application/json");

			if (map_name.length() > 0)
				stack_->statistics(j, protocol, map_name, limit);
			else
				stack_->statistics(j, protocol, level, limit);

			out << j;
		} else {
			// Check if protocol is set to something
			response.set(bhttp::field::content_type, "text/plain");

			stack_->statistics(out, protocol, level, limit);
		}
		response.set(bhttp::field::cache_control, "no-store");
		response.result(bhttp::status::ok);
	} else {
		if (!jsonize) {
			response.set(bhttp::field::content_type, "text/html");

			out << "<html><body>\n"
				<< "<script>\n"
				<< "setTimeout(function() {\n"
				<< "  window.location.href = '" << generate_redirection_url(request) << "';\n"
				<< "}, " << HTTPSession::script_delay_error << ");\n"
				<< "</script>\n"
				<< "<p>No protocol name set.</p>\n"
				<< "</body></html>";
		}
		response.result(bhttp::status::bad_request);
	}
	boost::beast::ostream(response.body()) << out.str();
	response.content_length(response.payload_size());
}

// /v1/summary

void HTTPSession::show_summary(bhttp::response<bhttp::dynamic_body> &response) {

        std::ostringstream out;
        bool jsonize = false;

        if (boost::beast::string_view accept = request[bhttp::field::accept]; accept.length() > 0)
                if (std::size_t found = accept.find("application/json"); found != std::string::npos)
                        jsonize = true;

        if (jsonize) {
                Json j;
                response.set(bhttp::field::content_type, "application/json");

		pdis_->statistics(j);

                out << j;
        } else {
                response.set(bhttp::field::content_type, "text/plain");
		pdis_->statistics(out);
	}

	response.set(bhttp::field::cache_control, "no-store");
	boost::beast::ostream(response.body()) << out.str();
	response.content_length(response.payload_size());
        response.result(bhttp::status::ok);
}

// /v1/system
void HTTPSession::show_system(bhttp::response<bhttp::dynamic_body> &response) {

        std::ostringstream out;
        bool jsonize = false;

        if (boost::beast::string_view accept = request[bhttp::field::accept]; accept.length() > 0)
                if (std::size_t found = accept.find("application/json"); found != std::string::npos)
                        jsonize = true;

        if (jsonize) {
                Json j;
                response.set(bhttp::field::content_type, "application/json");

		system->statistics(j);

                out << j;
        } else {
                response.set(bhttp::field::content_type, "text/plain");
		system->statistics(out);
	}

	response.set(bhttp::field::cache_control, "max-age=350");
	boost::beast::ostream(response.body()) << out.str();
	response.content_length(response.payload_size());
        response.result(bhttp::status::ok);
}

void HTTPSession::show_uris(bhttp::response<bhttp::dynamic_body> &response) {

        std::ostringstream out;
        bool jsonize = false;
	std::ostringstream baseuri;

	baseuri << "http://";

	if (boost::beast::string_view hostname = request[bhttp::field::host]; hostname.length() > 0)
		baseuri << hostname;
	else
		baseuri << socket_.local_endpoint().address().to_string() << ":" << socket_.local_endpoint().port();

        if (boost::beast::string_view accept = request[bhttp::field::accept]; accept.length() > 0)
                if (std::size_t found = accept.find("application/json"); found != std::string::npos)
                        jsonize = true;

	std::ostringstream evidences_url;

	evidences_url << baseuri.str() << HTTPUris::show_evidences << "." << getpid() << ".pcap";

        if (jsonize) {
                nlohmann::json j;
                response.set(bhttp::field::content_type, "application/json");

		j["uris"] = {
			{ std::string(baseuri.str() + HTTPUris::show_protocols_summary.data()), "Protocols summary" },
			{ std::string(baseuri.str() + HTTPUris::show_protocol.data()), "Protocol summary" },
			{ std::string(baseuri.str() + HTTPUris::show_network_flows.data()), "Network flows" },
			{ std::string(baseuri.str() + HTTPUris::show_summary.data()), "Summary" },
			{ std::string(baseuri.str() + HTTPUris::show_system.data()), "System" },
			{ std::string(baseuri.str() + HTTPUris::pcapfile.data()), "Upload pcapfile" },
			{ evidences_url.str(), "Evidences" },
#if defined(PYTHON_BINDING)
			{ std::string(baseuri.str() + HTTPUris::python_script.data()), "Upload python code" },
			{ std::string(baseuri.str() + HTTPUris::show_python_code.data()), "Python code" },
			{ std::string(baseuri.str() + HTTPUris::show_python_locals.data()), "Python locals" },
			{ std::string(baseuri.str() + HTTPUris::show_anomalies.data()), "Anomalies" },
#endif
			{ std::string(baseuri.str() + HTTPUris::show_network_flow.data()), "Network flow" },
			{ std::string(baseuri.str() + HTTPUris::show_current_packet.data()), "Current packet" },
			{ std::string(baseuri.str() + HTTPUris::show_logs.data()), "Logs" },
			{ std::string(baseuri.str() + HTTPUris::show_health.data()), "Health" }
		};

                out << j;
        } else {
                response.set(bhttp::field::content_type, "text/html");

		out << "<html><head><title>"
			<< "AIEngine " VERSION " running on " << system->getOSName()
			<< " kernel " << system->getReleaseName()
			<< " " << system->getVersionName()
			<< " " << system->getMachineName()
			<< " hostname " << system->getHostName()
			<< "</title></head>\n<body><center>\n"
			<< "<a href=\"" << baseuri.str() << HTTPUris::show_protocols_summary << "\">Protocols summary</a><br>\n"
			<< "<a href=\"" << baseuri.str() << HTTPUris::show_protocol << "\">Protocol summary</a><br>\n"
			<< "<a href=\"" << baseuri.str() << HTTPUris::show_network_flows << "\">Network flows</a><br>\n"
#if defined(PYTHON_BINDING)
			<< "<a href=\"" << baseuri.str() << HTTPUris::show_anomalies << "\">Anomalies</a><br>\n"
#endif
			<< "<a href=\"" << baseuri.str() << HTTPUris::show_summary << "\">Summary</a><br>\n"
			<< "<a href=\"" << baseuri.str() << HTTPUris::show_system << "\">System</a><br>\n"
			<< "<a href=\"" << baseuri.str() << HTTPUris::pcapfile << "\">Upload pcapfile</a><br>\n"
			<< "<a href=\"" << evidences_url.str() << "\">Evidences</a><br>\n"
#if defined(PYTHON_BINDING)
			<< "<a href=\"" << baseuri.str() << HTTPUris::python_script << "\">Python code</a><br>\n"
			<< "<a href=\"" << baseuri.str() << HTTPUris::show_python_code << "\">Current code</a><br>\n"
			<< "<a href=\"" << baseuri.str() << HTTPUris::show_python_locals << "\">Python locals</a><br>\n"
#endif
			<< "<a href=\"" << baseuri.str() << HTTPUris::show_network_flow << "\">Network flow</a><br>\n"
			<< "<a href=\"" << baseuri.str() << HTTPUris::show_current_packet << "\">Current packet</a><br>\n"
			<< "<a href=\"" << baseuri.str() << HTTPUris::show_logs << "\">Logs</a><br>\n"
#if defined(PYTHON_BINDING)
			<< "<a href=\"" << baseuri.str() << HTTPUris::show_python_help << "\">Help</a><br>\n"
#endif
			<< "</center></body>\n</html>\n";
	}

	response.set(bhttp::field::cache_control, "public");
	boost::beast::ostream(response.body()) << out.str();
	response.content_length(response.payload_size());
        response.result(bhttp::status::ok);
}

void HTTPSession::show_pcapfile(bhttp::response<bhttp::dynamic_body> &response) {

        std::ostringstream html;

        response.set(bhttp::field::content_type, "text/html");

        html << "<html><head><title>Upload pcapfile</title></head>\n<body><center>\n"
		<< "<form id=\"uploadpcapfile\" enctype=\"multipart/form-data\" method=\"post\" >\n"
		<< " <input id=\"fileupload\" name=\"myfile\" type=\"file\" />\n"
		<< "<input type=\"submit\" value=\"submit\" id=\"submit\" />\n"
		<< "</form>\n"
		<< "</center></body>\n</html>\n";

	response.set(bhttp::field::cache_control, "public");
	boost::beast::ostream(response.body()) << html.str();
	response.content_length(response.payload_size());
        response.result(bhttp::status::ok);
}

#if defined(PYTHON_BINDING)

void HTTPSession::show_python_script(bhttp::response<bhttp::dynamic_body> &response) {

        std::ostringstream html;
	int delay = HTTPSession::script_delay_ok;
	std::string uri(request.target());

        response.set(bhttp::field::content_type, "text/html");

        // Check if there is parameters on the URI
        uri.erase(0, HTTPUris::python_script.length());

        // Check if the URI contains parameters
        if (uri.find("?") != std::string::npos) {
                std::map<std::string, std::string> parameters;

                uri.erase(std::remove(uri.begin(), uri.end(), '?'), uri.end());

                parse_arguments(uri, parameters);

                if (auto it = parameters.find("name"); it != parameters.end())
			delay = std::atoi(it->second.c_str());
	}

        html << "<html><head><title>Python code</title></head>\n<body><center>\n"
		<< "<form id=\"uploadcode\" enctype=\"multipart/form-data\" method=\"post\" action=\"python_code?delay=" << delay << "\" >\n"
		<< "<label>Python code</label><br>\n"
		<< "<textarea type=\"text\" id=\"code\" name=\"code\" rows=\"32\" cols=\"64\">\n"
		<< "</textarea><br>\n"
		<< "<input type=\"submit\" value=\"submit\" id=\"submit\" />\n"
		<< "</form>\n"
		<< "</center></body>\n</html>\n";

	response.set(bhttp::field::cache_control, "public");
	boost::beast::ostream(response.body()) << html.str();
	response.content_length(response.payload_size());
        response.result(bhttp::status::ok);
}

boost::python::object dir(boost::python::object object) {
	boost::python::handle<> handle(PyObject_Dir(object.ptr()));
	return boost::python::object(handle);
}

bool callable(boost::python::object object) {
	return 1 == PyCallable_Check(object.ptr());
}

void HTTPSession::show_python_locals(bhttp::response<bhttp::dynamic_body> &response) {

        std::ostringstream out;
        bool jsonize = false;
        std::string uri(request.target());
	std::map<std::string, std::string> items;
	std::string object_name = "";

        if (boost::beast::string_view accept = request[bhttp::field::accept]; accept.length() > 0)
                if (std::size_t found = accept.find("application/json"); found != std::string::npos)
                        jsonize = true;

	// Check if there is parameters on the URI
        uri.erase(0, HTTPUris::show_python_locals.length());

        // Check if the URI contains parameters
        if (uri.find("?") != std::string::npos) {
                std::map<std::string, std::string> parameters;
		std::string name = "";
		std::string property_name = "";

                uri.erase(std::remove(uri.begin(), uri.end(), '?'), uri.end());

                parse_arguments(uri, parameters);

                if (auto it = parameters.find("name"); it != parameters.end())
                        name = it->second;

                if (auto it = parameters.find("property"); it != parameters.end())
			property_name = it->second;

		if (name.length() > 0) {
			boost::python::dict locals(boost::python::borrowed(PyEval_GetLocals()));
			response.result(bhttp::status::not_found);

			// Search on locals if we have that object
			boost::python::list keys = boost::python::list(locals.keys());
			bool found = false;
			for (int i = 0; i < len(keys); ++i) {
				boost::python::extract<std::string> extractor(keys[i]);
				if (extractor.check()) {
					std::string key = extractor();

					if (key.compare(name) == 0) {
						found = true;
						break;
					}
				}
			}
			if (found) { // The object exists on locals
				object_name = name;
				boost::python::extract<boost::python::object> extractor(locals[name]);
				boost::python::object self = extractor();
				boost::python::extract<std::string> extract(self.attr("__class__").attr("__name__"));

				if (extract.check()) {
					std::string object_type = extract();
					typedef boost::python::stl_input_iterator<boost::python::str> iterator_type;
  					for (iterator_type name(dir(self)), end; name != end; ++name) { // Loop over the properties
						boost::python::extract<std::string> extract(*name);
						if (extract.check()) {
							std::string attribute_name = extract();
							if (property_name.length() > 0) {
								if (property_name.compare(attribute_name) == 0) {
									boost::python::object prop = self.attr(attribute_name.c_str());
									items[attribute_name] = boost::python::extract<std::string>(boost::python::str(prop))();
									break;
								}
							} else {
								if ((attribute_name.compare(0, 2, "__") != 0) and !callable(self.attr(*name))) {
									boost::python::object prop = self.attr(attribute_name.c_str());
									items[attribute_name] = boost::python::extract<std::string>(boost::python::str(prop))();
								}
							}
						}
					}
				}
				response.result(bhttp::status::ok);
			}
		}
        } else { // There is no parameters so just show the locals
		boost::python::dict locals(boost::python::borrowed(PyEval_GetLocals()));
		boost::python::list keys = boost::python::list(locals.keys());

		for (int i = 0; i < len(keys); ++i) {
			boost::python::extract<std::string> extractor(keys[i]);
			if (extractor.check()) {
				std::string key = extractor();

				boost::python::extract<boost::python::object> objectExtractor(locals[key]);
				boost::python::object obj = objectExtractor();
				std::string object_classname = boost::python::extract<std::string>(obj.attr("__class__").attr("__name__"));

				if (key.rfind("__", 0) != 0)
					items[key] = object_classname;
			}
		}
        	response.result(bhttp::status::ok);
	}

	if (jsonize) {
                nlohmann::json j;
                response.set(bhttp::field::content_type, "application/json");

		if (object_name.length() > 0)
			j["properties"] = items;
		else
			j["objects"] = items;

		out << j;
	} else {
        	response.set(bhttp::field::content_type, "text/plain");
		if (object_name.length() > 0) {
			out << "Object " << object_name << "\n";

			for (auto &item: items)
				out << "\t" << item.first << " = " << item.second << "\n";
		} else {
			out << "Python objects" << "\n";

			for (auto &item: items)
				out << "\t" << item.first << ": " << item.second << "\n";
		}
	}

	response.set(bhttp::field::cache_control, "no-store");
	boost::beast::ostream(response.body()) << out.str();
	response.content_length(response.payload_size());
}

void HTTPSession::show_python_help(bhttp::response<bhttp::dynamic_body> &response) {

        std::ostringstream out;
        std::ostringstream path, filename;
        std::string uri(request.target());
	std::string class_name = "";
	boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();

        // Check if there is a parameter on the URI
        uri.erase(0, HTTPUris::show_python_help.length());

        // Check if the URI contains parameters
        if (uri.find("?") != std::string::npos) {
                std::map<std::string, std::string> parameters;

                uri.erase(std::remove(uri.begin(), uri.end(), '?'), uri.end());

                parse_arguments(uri, parameters);

                if (auto it = parameters.find("class"); it != parameters.end())
                        class_name = it->second;
	}

        std::string code("import pydoc\npydoc.help(pyaiengine");
	if (class_name.length() > 0)
		code += "." + class_name;

	code += ")";

	path << datapath.data() << "-" << getpid();

	if (std::filesystem::exists(path.str()) == false)
		std::filesystem::create_directory(path.str());

	filename << path.str() << "/help."
		<< now.time_of_day().hours() << "."
		<< now.time_of_day().minutes() << "."
		<< now.time_of_day().seconds() << ".out";

        bool executed = run_python_code(filename.str(), code);

        if (executed) {
		std::ifstream t(filename.str());

		out << t.rdbuf();

		t.close();

		response.result(bhttp::status::ok);
        } else {
                response.result(bhttp::status::bad_request);
        }

	response.set(bhttp::field::cache_control, "public");
        response.set(bhttp::field::content_type, "text/plain");
	boost::beast::ostream(response.body()) << out.str();
	response.content_length(response.payload_size());
}

void HTTPSession::show_python_code(bhttp::response<bhttp::dynamic_body> &response) {

	std::string code("import os\nprint(os.path.realpath(__file__), end='')");
        std::ostringstream path, filename;
        std::ostringstream script_filename;
        std::ostringstream out;
	boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();

	path << datapath.data() << "-" << getpid();

	if (std::filesystem::exists(path.str()) == false)
		std::filesystem::create_directory(path.str());

	filename << path.str() << "/show."
		<< now.time_of_day().hours() << "."
		<< now.time_of_day().minutes() << "."
		<< now.time_of_day().seconds() << ".out";

        bool executed = run_python_code(filename.str(), code);

        if (executed) {
                std::ifstream t(filename.str());

		script_filename << t.rdbuf();

                t.close();

		std::ifstream sfile(script_filename.str());

		out << sfile.rdbuf();

		sfile.close();

                response.result(bhttp::status::ok);
        } else {
                response.result(bhttp::status::bad_request);
        }

	response.set(bhttp::field::cache_control, "public");
        response.set(bhttp::field::content_type, "text/plain");
	boost::beast::ostream(response.body()) << out.str();
	response.content_length(response.payload_size());
}

#endif

// Now POST methods

void HTTPSession::upload_pcapfile(bhttp::response<bhttp::dynamic_body> &response) {

        std::size_t off, roff, foff_s, foff_e;
        boost::beast::string_view ct = request[bhttp::field::content_type];
	response.set(bhttp::field::content_type, "text/plain");
        response.content_length(0);

#if defined(PYTHON_BINDING)
	if (!is_authorized(response))
		return;
#endif
        if (off = ct.find("multipart/form-data"); off == std::string::npos) {
                response.result(bhttp::status::unsupported_media_type);
                return;
        }

        if (off = ct.find("boundary="); off == std::string::npos) {
                response.result(bhttp::status::bad_request);
                return;
        }

        // Now get the filename and the content type to verify that is a pcap file

        std::ostringstream body;
#if BOOST_BEAST_VERSION >= 266
        body << boost::beast::make_printable(request.body().data());
#else
        body << boost::beast::buffers(request.body().data());
#endif
        std::string boundary(ct.substr(off + 9));

        if (off = body.str().find(boundary); off == std::string::npos) {
                response.result(bhttp::status::bad_request);
                return;
        }

        off += boundary.length() + 2;

        if (off = body.str().find("\r\n\r\n", off); off == std::string::npos) {
                response.result(bhttp::status::bad_request);
                return;
        }

        if (roff = body.str().rfind(boundary); roff == std::string::npos) {
                response.result(bhttp::status::bad_request);
                return;
        }

        // extract the file name
        if (foff_s = body.str().find("filename=", boundary.length() + 4); foff_s == std::string::npos) {
                response.result(bhttp::status::bad_request);
                return;
        }

        if (foff_e = body.str().find("\r\n", foff_s); foff_e == std::string::npos) {
                response.result(bhttp::status::bad_request);
                return;
        }

        std::string filename(body.str().substr(foff_s + 10, foff_e - (foff_s + 10) - 1));
        std::ostringstream path;

	path << datapath.data() << "-" << getpid();

	if (std::filesystem::exists(path.str()) == false)
		std::filesystem::create_directory(path.str());

        path << "/" << filename;
        std::ofstream ofs (path.str(), std::ofstream::out);

        int len = body.str().length() - (off + 4 + boundary.length() + 2 + 6);
        ofs << body.str().substr(off + 4, len);

        ofs.close();

        // Close the other device or pcapfiles
        pdis_->close();

        // Open the new one
        pdis_->open(path.str());

        // Run it
        pdis_->run();

        std::ostringstream html;

        response.set(bhttp::field::content_type, "text/html");

	html << "<html><body>\n"
		<< "<script>\n"
		<< "setTimeout(function() {\n"
		<< "  window.location.href = '" << generate_redirection_url(request) << "';\n"
		<< "}, " << HTTPSession::script_delay_ok << ");\n"
		<< "</script>\n"
		<< "<p>Pcap upload successfully.</p>\n"
		<< "</body></html>";

	boost::beast::ostream(response.body()) << html.str();
	response.content_length(response.payload_size());
        response.result(bhttp::status::ok);
}

#if defined(PYTHON_BINDING)

bool HTTPSession::run_python_code(const std::string &filename, const std::string &code) const {

	bool executed = false;
	std::ostringstream scode;
	int pid = getpid();
#if defined(IS_DARWIN)
	boost::system::error_code ec;

	boost::filesystem::resize_file(filename, 0, ec); // truncate file
#else
	std::error_code ec;

	std::filesystem::resize_file(filename, 0, ec); // truncate file
#endif
	// We create a execution with a redirection of the output
	// bear in mind that the user could put print statements
	// so we need to redirect properly the things
	scode << "import sys\n"
		<< "output_" << pid << " = open('" << filename << "', 'w')\n"
		<< "temp_" << pid << " = sys.stdout\n"
		<< "sys.stdout = output_" << pid << "\n"
		<< code << "\n"
		<< "output_" << pid << ".close()\n"
		<< "sys.stdout = temp_" << pid << "\n"
		<< "del temp_" << pid << "\n"
		<< "del output_" << pid << "\n";

	// Check if we need to write the code on disk
	if (pdis_->getLogRestApiPythonCode()) {
		boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
		std::ostringstream path, filename;

		path << datapath.data() << "-" << getpid();

		filename << path.str() << "/code."
			<< now.time_of_day().hours() << "."
			<< now.time_of_day().minutes() << "."
			<< now.time_of_day().seconds() << ".py";

		std::ofstream infile(filename.str());

		infile << "# Code sent from " << ipsrc_ << "\n";
		infile << scode.str();

		infile.close();
	}

	std::ofstream term(filename, std::ios_base::out);
	auto &oldout = OutputManager::getInstance()->out();
	OutputManager::getInstance()->setOutput(term);

        try {
		[[maybe_unused]] PyGilContext gil_lock;

		// Retrieve the main module.
		boost::python::object main = boost::python::import("__main__");
		boost::python::object global(main.attr("__dict__"));

		boost::python::exec(scode.str().c_str(), global, global);
		executed = true;
        } catch (boost::python::error_already_set const &) {
		PyObject *ptype, *pvalue, *ptraceback;
		PyErr_Fetch(&ptype, &pvalue, &ptraceback);
		PyObject* str_exc_type = PyObject_Repr(pvalue); //Now a unicode object
		PyObject* pyStr = PyUnicode_AsEncodedString(str_exc_type, "utf-8", "Error ~");

		term << PyBytes_AS_STRING(pyStr) << std::endl;

		Py_XDECREF(str_exc_type);
		Py_XDECREF(pyStr);
        }

	term.close();
	OutputManager::getInstance()->setOutput(oldout);

	return executed;
}

// This function makes the Authorization of the requests
// Bear in mind that browsers will do a base64(username:password)
// so the value that needs to be stored should contain the : character
bool HTTPSession::is_authorized(bhttp::response<bhttp::dynamic_body> &response) const {

	std::string token(pdis_->getRawAuthorizationCodeToken());

	// Check if we have an authorization code token set on the PacketDispatcher
	if (token.length() > 0) {
		if (boost::beast::string_view authorization = request[bhttp::field::authorization]; authorization.length() > 0) {
			// Check if we have Basic or Bearer authentication tokens
			std::size_t pos = authorization.find("Basic ");
			uint8_t offset = 6;

			if (pos == std::string::npos) {
				pos = authorization.find("Bearer ");
				offset = 7;
			}
			if (pos != std::string::npos) { // There is Basic or Bearer
				int token_length = authorization.length() - offset;
				if (authorization.compare(offset, token_length, token) != 0) {
					boost::beast::ostream(response.body()) << "Wrong credentials";
					response.content_length(response.payload_size());
					response.result(bhttp::status::forbidden);
					return false;
				}
				return true; // The token is correct
			}
		}
		boost::beast::ostream(response.body()) << "Wrong authorization credentials";
		response.content_length(response.payload_size());
		response.set(bhttp::field::www_authenticate, "Basic realm='AIEngine'");
		response.result(bhttp::status::unauthorized);
		return false;
	}
	return true;
}

void HTTPSession::upload_python_script(bhttp::response<bhttp::dynamic_body> &response) {

        std::size_t off, roff;
        boost::beast::string_view ct = request[bhttp::field::content_type];
	std::string code;
	std::string uri(request.target());
        std::ostringstream body;
	int delay_ok = HTTPSession::script_delay_ok;
#if BOOST_BEAST_VERSION >= 266
        body << boost::beast::make_printable(request.body().data());
#else
        body << boost::beast::buffers(request.body().data());
#endif
	response.set(bhttp::field::content_type, "text/plain");
        response.content_length(0);

#if defined(PYTHON_BINDING)
	if (!is_authorized(response))
		return;
#endif
        // Check if there is parameters on the URI
        uri.erase(0, HTTPUris::python_script.length());

        // Check if the URI contains parameters
        if (uri.find("?") != std::string::npos) {
                std::map<std::string, std::string> parameters;

                uri.erase(std::remove(uri.begin(), uri.end(), '?'), uri.end());

                parse_arguments(uri, parameters);

                if (auto it = parameters.find("delay"); it != parameters.end())
			delay_ok = std::atoi(it->second.c_str());
        }

        if (off = ct.find("multipart/form-data"); off != std::string::npos) {
		if (off = ct.find("boundary="); off == std::string::npos) {
                	response.result(bhttp::status::bad_request);
                	return;
        	}
        	std::string boundary(ct.substr(off + 9));

        	if (off = body.str().find(boundary); off == std::string::npos) {
                	response.result(bhttp::status::bad_request);
                	return;
        	}

		// Just jump to the next line and dont process Content-disposition
		if (off = body.str().find("\r\n\r\n", off); off == std::string::npos) {
                	response.result(bhttp::status::bad_request);
                	return;
		}

        	if (roff = body.str().rfind(boundary); roff == std::string::npos) {
                	response.result(bhttp::status::bad_request);
                	return;
        	}

		code = body.str().substr(off, roff - off - 4);

        } else if (off = ct.find("text/python"); off != std::string::npos) {
		code = body.str();
	} else {
                response.result(bhttp::status::unsupported_media_type);
                return;
	}

	if (code.length() > 0) {
		std::ostringstream filename, path,  message, html;
		int delay = 0;
		boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
		response.set(bhttp::field::content_type, "text/html");

		path << datapath.data() << "-" << getpid();

		if (std::filesystem::exists(path.str()) == false)
			std::filesystem::create_directory(path.str());

		filename << path.str() << "/code."
			<< now.time_of_day().hours() << "."
			<< now.time_of_day().minutes() << "."
			<< now.time_of_day().seconds() << ".out";

		bool executed = run_python_code(filename.str(), code);

		std::ifstream t(filename.str());

		if (executed) {
			message << "Python code loaded successfully\n";
			delay = delay_ok;
			response.result(bhttp::status::ok);
		} else {
			message << "Python code can not be loaded\n";
			delay = HTTPSession::script_delay_error;
			response.result(bhttp::status::bad_request);
		}

		html << "<html><body>\n"
			<< "<script>\n"
			<< "setTimeout(function() {\n"
			<< "  window.location.href = '" << generate_redirection_url(request) << "';\n"
			<< "}, " << delay << ");\n"
			<< "</script>\n"
			<< "<p>" << message.str() << "</p>\n"
			<< "<pre>" << t.rdbuf() << "</pre>\n"
			<< "</body></html>";

		t.close();

		boost::beast::ostream(response.body()) << html.str();
		response.content_length(response.payload_size());
        } else {
		response.result(bhttp::status::bad_request);
		boost::beast::ostream(response.body()) << "No python code provided";
		response.content_length(response.payload_size());
	}
}

void HTTPSession::set_python_object_property(bhttp::response<bhttp::dynamic_body> &response) {

	std::ostringstream out;
	std::string uri(request.target());
	std::string name = "";
	std::string property_name = "";
	std::string property_value = "";

	if (!is_authorized(response))
		return;

        // Check if there is parameters on the URI
        uri.erase(0, HTTPUris::show_python_locals.length());

        // Check if the URI contains parameters
        if (uri.find("?") != std::string::npos) {
		std::map<std::string, std::string> parameters;

		uri.erase(std::remove(uri.begin(), uri.end(), '?'), uri.end());

		parse_arguments(uri, parameters);

		if (auto it = parameters.find("name"); it != parameters.end())
			name = it->second;

		if (auto it = parameters.find("property"); it != parameters.end())
			property_name = it->second;

		if (auto it = parameters.find("value"); it != parameters.end())
			property_value = it->second;
	}
	if ((name.length() > 0)and(property_name.length() > 0)and(property_value.length() > 0)) {
		std::ostringstream code, path;
		boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
		std::ostringstream filename;

		path << datapath.data() << "-" << getpid();

		code << name << "." << property_name << " = " << property_value;

		if (std::filesystem::exists(path.str()) == false)
			std::filesystem::create_directory(path.str());

		filename << path.str() << "/propertycode."
			<< now.time_of_day().hours() << "."
			<< now.time_of_day().minutes() << "."
			<< now.time_of_day().seconds() << ".out";

		bool executed = run_python_code(filename.str(), code.str());

		std::ifstream t(filename.str());

		out << t.rdbuf();

		t.close();

		if (executed) {
			response.set(bhttp::field::location, generate_redirection_url(request));
			response.result(bhttp::status::ok);
		} else
			response.result(bhttp::status::bad_request);
	} else {
		out << "Can not change the property";
		response.result(bhttp::status::not_found);
	}
	boost::beast::ostream(response.body()) << out.str();
	response.content_length(response.payload_size());
}

// /v1/anomalies
void HTTPSession::show_anomalies(bhttp::response<bhttp::dynamic_body> &response) {

        std::ostringstream out;
        bool jsonize = false;

        if (boost::beast::string_view accept = request[bhttp::field::accept]; accept.length() > 0)
                if (std::size_t found = accept.find("application/json"); found != std::string::npos)
                        jsonize = true;

        if (jsonize) {
                Json j;
                response.set(bhttp::field::content_type, "application/json");

                stack_->getAnomalyManager()->statistics(j);

                out << j;
        } else {
                response.set(bhttp::field::content_type, "text/plain");

                stack_->getAnomalyManager()->statistics(out);
        }

	response.set(bhttp::field::cache_control, "no-store");
	boost::beast::ostream(response.body()) << out.str();
	response.content_length(response.payload_size());
        response.result(bhttp::status::ok);
}

void HTTPSession::manage_alarms(bhttp::response<bhttp::dynamic_body> &response) {

        boost::beast::string_view content_type = request[bhttp::field::content_type];
        response.set(bhttp::field::content_type, "text/plain");
        response.content_length(0);

	if (content_type.compare("application/json") != 0) {
		response.result(bhttp::status::unsupported_media_type);
		return;
	}

        if (!is_authorized(response))
                return;

	if (pdis_->getAlarmCallback()->haveCallback() == false) {
		std::string_view message = "No alarm callback set";

                response.set(bhttp::field::content_type, "text/html");
                response.result(bhttp::status::not_found);
                boost::beast::ostream(response.body()) << message;
                response.content_length(message.length());
                return;
	}

        std::ostringstream body;
#if BOOST_BEAST_VERSION >= 266
        body << boost::beast::make_printable(request.body().data());
#else
        body << boost::beast::buffers(request.body().data());
#endif
        nlohmann::json j = nlohmann::json::parse(body.str(), nullptr, false);
        if (j.is_discarded()) {
		std::string_view message = "Not valid json content";

                response.set(bhttp::field::content_type, "text/html");
                response.result(bhttp::status::bad_request);
                boost::beast::ostream(response.body()) << message;
                response.content_length(message.length());
                return;
	}

	// The payload is json but we stil send the string
	pdis_->getAlarmCallback()->executeCallback(body.str());

	// If the callback has been disable we need to notify the client
	if (pdis_->getAlarmCallback()->haveCallback())
		response.result(bhttp::status::ok);
	else {
		std::string_view message = "Alarm callback has been disable";

                response.set(bhttp::field::content_type, "text/html");
                boost::beast::ostream(response.body()) << message;
                response.result(bhttp::status::bad_request);
                response.content_length(message.length());
	}
}

#endif

SharedPointer<Flow> HTTPSession::get_flow_from_uri(const std::string &uri) const {

	FlowSearchOptions fsos;
        std::string uuri = uri;
	std::string addrsrc = "";
	std::string addrdst = "";
	std::string ipsrc, ipdst;

        // Check if the URI contains a flow id parameters
        if (uuri.find("?") != std::string::npos) {
		std::map<std::string, std::string> parameters;

		uuri.erase(std::remove(uuri.begin(), uuri.end(), '?'), uuri.end());

		parse_arguments(uuri, parameters);

		if (auto it = parameters.find("id"); it != parameters.end()) {
			std::string flowid = it->second;

			// The URI can have encoded characters for [] so need to replace them

			boost::replace_all(flowid, "%5B", "[");
			boost::replace_all(flowid, "%5D", "]");
			boost::replace_all(flowid, "%3A", ":");

			// Do a basic parsing
			qi::phrase_parse(flowid.begin(), flowid.end(),
				qi::lit("[") >> *(~qi::char_("") - "]") >> qi::lit("]:") >>
				qi::int_ >> qi::lit(":[") >> *(~qi::char_("") - "]"),
				qi::ascii::space,
				addrsrc, fsos.protocol, addrdst);

			if (std::size_t found = addrsrc.find_last_of(":"); found != std::string::npos) {
				ipsrc = addrsrc.substr(0, found);
				fsos.portsrc = std::atoi(std::string(addrsrc.substr(found + 1)).c_str());
			}

			if (std::size_t found = addrdst.find_last_of(":"); found != std::string::npos) {
				ipdst = addrdst.substr(0, found);
				fsos.portdst = std::atoi(std::string(addrdst.substr(found + 1)).c_str());
			}

			fsos.ipsrc = ipsrc.c_str();
			fsos.ipdst = ipdst.c_str();
		}
		if (auto it = parameters.find("tag"); it != parameters.end()) {
			fsos.tag = std::atoi(it->second.c_str());
			fsos.have_tag = true;
                }
        }

	return stack_->getFlow(fsos);
}

// URIS:
// /v1/flow?id=[10.42.0.42:42140]:6:[172.217.7.14:443]
// /v1/flow?id=[10.42.0.42:42140]:6:[172.217.7.14:443]&payload=true
// /v1/flow?id=[10.42.0.42:42140]:6:[172.217.7.14:443]&payload=true&len=32

void HTTPSession::show_network_flow(bhttp::response<bhttp::dynamic_body> &response) {

        std::ostringstream out;
        std::string uri(request.target());
        bool jsonize = false;
        std::string flowid;

        if (boost::beast::string_view accept = request[bhttp::field::accept]; accept.length() > 0)
                if (std::size_t found = accept.find("application/json"); found != std::string::npos)
                        jsonize = true;

        uri.erase(0, HTTPUris::show_network_flow.length());

	if (SharedPointer<Flow> flow = get_flow_from_uri(uri); flow) {
        	response.result(bhttp::status::ok);
		std::time_t duration = flow->getLastPacketTime() - flow->getArriveTime();
		int timeout = stack_->getFlowsTimeout();;
		std::string_view status = flow->status(request_time_, timeout);
		std::time_t last_packet_seen = request_time_ - flow->getLastPacketTime();
		bool show_l7_payload = false;
		uint16_t l7_payload_length = 0;

#if defined(BINDING)
		// Check if the URI contains the payload parameter and the length/len also
		if (uri.find("?") != std::string::npos) {
			std::map<std::string, std::string> parameters;

			uri.erase(std::remove(uri.begin(), uri.end(), '?'), uri.end());

			parse_arguments(uri, parameters);

			if (auto it = parameters.find("payload"); it != parameters.end())
				if ((it->second.compare("true") == 0) or (it->second.compare("True") == 0))
					show_l7_payload = true;

			if (auto it = parameters.find("len"); it != parameters.end())
				l7_payload_length = std::atoi(it->second.c_str());
		}

		// The user wants to see the L7 payload
		if ((show_l7_payload) and (!flow->l7_payload)) {
			if (total_l7_allocations < MAX_L7_ALLOCATIONS)
				flow->l7_payload = SharedPointer<L7Payload>(new L7Payload()); // Allocate the memory
			else
				AIWARN << "Total max of L7 allocations reach " << MAX_L7_ALLOCATIONS;
		} else if ((!show_l7_payload) and (flow->l7_payload))
			flow->l7_payload.reset(); // Destroy the memory
#endif
		// If the flow duration is zero means that the flow was very quick,
                // for example a DNS query could happen on the same second
		// leave the duration equals to zero
		if (duration == 0) duration = 1;

		if (jsonize) {
			Json j;
			response.set(bhttp::field::content_type, "application/json");

			flow->show(j);

			j["status"] = status;
			j["duration"] = (int)duration;
			j["last_packet_seen"] = (int)last_packet_seen;
#if defined(BINDING)
			if (flow->l7_payload) {
				std::vector<uint8_t> pkt;
				const uint8_t *payload = flow->l7_payload->payload();

				// Show the layer 7 payload

				if ((l7_payload_length > flow->l7_payload->length()) or (l7_payload_length == 0))
					l7_payload_length = flow->l7_payload->length();

				for (int i = 0; i < l7_payload_length; ++i)
					pkt.push_back(payload[i]);

				j["l7_payload"] = pkt;
			}
#endif
			out << j;
		} else {
			response.set(bhttp::field::content_type, "text/plain");
			std::string_view ev = flow->haveEvidence() ? "yes": "no";
			std::string_view reject = flow->isReject() or flow->isPartialReject() ? "yes": "no";
			std::string_view proto = flow->getProtocol() == IPPROTO_TCP ? "TCP": "UDP";
			std::string label = flow->getLabel();
			std::ostringstream outttl;

			outttl << "(" <<(int)flow->upstream_ttl << "," << (int)flow->downstream_ttl << ")";
#if defined(BINDING)
			std::string_view accepted = flow->isAccept() ? "yes": "no";
#endif
			out << "Network flow: " << *flow << "\n"
				<< "\t" << "Protocol:               " << std::setw(10) << proto << "\n"
				<< "\t" << "L7Protocol:           " << std::setw(12) << flow->getL7ProtocolName() << "\n"
#if defined(BINDING)
				<< "\t" << "Accept:                 " << std::setw(10) << accepted << "\n"
#endif
				<< "\t" << "Evidence:               " << std::setw(10) << ev << "\n"
				<< "\t" << "Reject:                 " << std::setw(10) << reject << "\n"
				<< "\t" << "Anomaly:    " << std::setw(22) << flow->getFlowAnomalyString() << "\n"
				<< "\t" << "Status:                 " << std::setw(10) << status << "\n"
				<< "\t" << "Up bytes:     " << std::setw(20) << flow->total_bytes[static_cast<int>(FlowDirection::FORWARD)] << "\n"
				<< "\t" << "Down bytes:   " << std::setw(20) << flow->total_bytes[static_cast<int>(FlowDirection::BACKWARD)] << "\n"
				<< "\t" << "Up packets:       " << std::setw(16) << flow->total_packets[static_cast<int>(FlowDirection::FORWARD)] << "\n"
				<< "\t" << "Down packets:     " << std::setw(16) << flow->total_packets[static_cast<int>(FlowDirection::BACKWARD)] << "\n"
#if defined(BINDING)
				<< "\t" << "Drop bytes:       " << std::setw(16) << flow->total_drop_bytes << "\n"
				<< "\t" << "Drop packets:     " << std::setw(16) << flow->total_drop_packets << "\n"
#endif
				<< "\t" << "Duration:              " << ElapsedTime().compute(duration) << "\n"
				<< "\t" << "Last packet seen:  " << std::setw(10) << (int)last_packet_seen << " secs\n"
				<< "\t" << "IP ttl:                 " << std::setw(10) << outttl.str() << "\n";

			if ((flow->getProtocol() == IPPROTO_TCP)and(flow->getTCPInfo())) {
				out << "\tTCP state:            " << std::setw(12) << flow->getTCPInfo()->getState() << "\n";
				out << "\tTCP: " << *flow->getTCPInfo() << "\n";
			} else if (auto ginfo = flow->getGPRSInfo(); ginfo)
				out << "\t" << "GPRS:" << *ginfo.get() << "\n";

			if (flow->ipset.lock())
				out << "\t" << "IPSet: " << std::setw(27) << flow->ipset.lock()->name() << "\n";

			if (label.length() > 0)
				out << "\t" << "Label: " << std::setw(27) << label << "\n";

			if (!flow->regex.expired())
				out << "\t" << "Regex:      " << std::setw(22) << flow->regex.lock()->name() << "\n";

			out << "\tLayer7:";
			flow->showL7(out);
#if defined(BINDING)
			if (flow->l7_payload) {
				// Show the layer 7 payload
				out << "\n\n";

				if ((l7_payload_length > flow->l7_payload->length()) or (l7_payload_length == 0))
					l7_payload_length = flow->l7_payload->length();

				showPayload(out, flow->l7_payload->payload(), l7_payload_length);
			}
#endif
		}
	} else {
		if (!jsonize) {
			out << "<html><body>\n"
				<< "<script>\n"
				<< "setTimeout(function() {\n"
				<< "  window.location.href = '" << generate_redirection_url(request) << "';\n"
				<< "}, " << HTTPSession::script_delay_error << ");\n"
				<< "</script>\n"
				<< "<p>Network flow not set or found</p>\n"
				<< "</body></html>";
                        response.set(bhttp::field::content_type, "text/html");
		}
                response.result(bhttp::status::not_found);
	}

	response.set(bhttp::field::cache_control, "no-store");
	boost::beast::ostream(response.body()) << out.str();
	response.content_length(response.payload_size());
}

void HTTPSession::handle_put_message(bhttp::response<bhttp::dynamic_body> &response) {

	if (check_operations(response, put_operations_) == false) {
		response.result(bhttp::status::bad_request);
		response.set(bhttp::field::content_type, "text/plain");
		boost::beast::ostream(response.body()) << "Invalid PUT resource '" << request.target() << "'";
		response.content_length(response.payload_size());
	}
}

void HTTPSession::update_network_flow(bhttp::response<bhttp::dynamic_body> &response) {

        boost::beast::string_view content_type = request[bhttp::field::content_type];

#if defined(PYTHON_BINDING)
	if (!is_authorized(response))
		return;
#endif
        if (content_type.compare("application/json") == 0) {
		std::string uri(request.target());
                std::ostringstream bodydata;

#if BOOST_BEAST_VERSION >= 266
        	bodydata << boost::beast::make_printable(request.body().data());
#else
        	bodydata << boost::beast::buffers(request.body().data());
#endif
		uri.erase(0, HTTPUris::show_network_flow.length());

		if (SharedPointer<Flow> flow = get_flow_from_uri(uri); flow) {
                	response.result(bhttp::status::ok);

                	nlohmann::json j = nlohmann::json::parse(bodydata.str(), nullptr, false);
                	if (j.is_discarded()) {
                        	std::string_view message = "Not valid json content";

                        	response.set(bhttp::field::content_type, "text/html");
                        	response.result(bhttp::status::bad_request);
                        	boost::beast::ostream(response.body()) << message;
                        	response.content_length(message.length());
                	} else {
				// The user wants to change the label of the flow
				if (j.find("label") != j.end(); j["label"].is_string())
					flow->setLabel(j["label"]);

				// The user wants to have evidences of the flow
				if (j.find("evidence") != j.end(); j["evidence"].is_boolean())
					flow->setEvidence(j["evidence"]);

				// The user wants to reset the TCP/UDP connection
				if (j.find("reject") != j.end(); j["reject"].is_boolean())
					flow->setReject(j["reject"]);
#if defined(PYTHON_BINDING)
				// The user wants to accept or drop the traffic of the flow
				if (j.find("accept") != j.end(); j["accept"].is_boolean())
					flow->setAccept(j["accept"]);

				// The user wants to detach the flow
				if (j.find("detach") != j.end(); j["detach"].is_boolean())
					if (j["detach"] == true)
						flow->detach();
#endif
			}
		} else
                	response.result(bhttp::status::not_found);
	} else if (content_type.compare("application/x-www-form-urlencoded") == 0) {
		std::string uri(request.target());

		uri.erase(0, HTTPUris::show_network_flow.length());

		if (SharedPointer<Flow> flow = get_flow_from_uri(uri); flow) {
			std::map<std::string, std::string> parameters;
			std::ostringstream bodydata;
			response.result(bhttp::status::ok);

#if BOOST_BEAST_VERSION >= 266
        		bodydata << boost::beast::make_printable(request.body().data());
#else
        		bodydata << boost::beast::buffers(request.body().data());
#endif
			parse_arguments(bodydata.str(), parameters);

			if (auto it = parameters.find("label"); it != parameters.end()){
				std::string label(it->second);

				boost::replace_all(label, "+", " ");

				flow->setLabel(label);
			}

			if (auto it = parameters.find("evidence"); it != parameters.end()) {
				if (boost::iequals(it->second, "true"))
					flow->setEvidence(true);
				else if (boost::iequals(it->second, "false"))
					flow->setEvidence(false);
			}

			if (auto it = parameters.find("reject"); it != parameters.end()) {
				if (boost::iequals(it->second, "true"))
					flow->setReject(true);
				else if (boost::iequals(it->second, "false"))
					flow->setReject(false);
			}
#if defined(PYTHON_BINDING)
			if (auto it = parameters.find("accept"); it != parameters.end()) {
				if (boost::iequals(it->second, "true"))
					flow->setAccept(true);
				else if (boost::iequals(it->second, "false"))
					flow->setAccept(false);
			}

			if (auto it = parameters.find("detach"); it != parameters.end())
				if (boost::iequals(it->second, "true"))
					flow->detach();
#endif
                }
	} else
		response.result(bhttp::status::unsupported_media_type);
}

void HTTPSession::show_current_packet(bhttp::response<bhttp::dynamic_body> &response) {

        std::ostringstream out;
        std::vector<std::string> items;
        std::string uri(request.target());
        bool jsonize = false;

        if (boost::beast::string_view accept = request[bhttp::field::accept]; accept.length() > 0)
                if (std::size_t found = accept.find("application/json"); found != std::string::npos)
                        jsonize = true;

        if (jsonize) {
		Json j;
                response.set(bhttp::field::content_type, "application/json");

		pdis_->showCurrentPayloadPacket(j);
		out << j;
	} else {
		response.set(bhttp::field::content_type, "text/plain");

		pdis_->showCurrentPayloadPacket(out);
        }

	response.set(bhttp::field::cache_control, "no-store");
	boost::beast::ostream(response.body()) << out.str();
	response.content_length(response.payload_size());
}

void HTTPSession::show_logs(bhttp::response<bhttp::dynamic_body> &response) {

	std::ostringstream out;
	std::string uri(request.target());
	int limit = 16;
	glob_t globlist;
	const char *pattern_log_file = "aiengine-*.log";

        response.result(bhttp::status::ok);
        response.set(bhttp::field::content_type, "text/plain");

	// Check if there is parameters on the URI
        uri.erase(0, HTTPUris::show_logs.length());

        // Check if the URI contains parameters
        if (uri.find("?") != std::string::npos) {
		std::map<std::string, std::string> parameters;

                uri.erase(std::remove(uri.begin(), uri.end(), '?'), uri.end());

		parse_arguments(uri, parameters);

		if (auto it = parameters.find("limit"); it != parameters.end())
			limit = std::atoi(it->second.c_str());
	}

	if (glob(pattern_log_file, 0, NULL, &globlist) == 0) {
		std::string filename = globlist.gl_pathv[globlist.gl_pathc - 1];

		FILE *fp = fopen(filename.c_str(), "r");
		if (fp) {
			fseek(fp, 0, SEEK_END);
			char buffer[512];
			auto pos = ftell(fp);
			int count = 0;

			while (pos) {
				if (!fseek(fp, --pos, SEEK_SET)) {
					if (fgetc(fp) == '\n')
						if (count++ == limit)
							break;
				}
			}
			if (count <= limit)
				fseek(fp, 0, SEEK_SET);

			while (fgets(buffer, sizeof(buffer), fp))
				out << buffer;

			fclose(fp);
		} else
			response.result(bhttp::status::not_found);
	} else
		response.result(bhttp::status::not_found);

	response.set(bhttp::field::cache_control, "no-store");
	boost::beast::ostream(response.body()) << out.str();
	response.content_length(response.payload_size());
}

void HTTPSession::show_health(bhttp::response<bhttp::dynamic_body> &response) {

	// Just respond 200 that all is good :)
	// Just respond 200 that all is good :)
	response.set(bhttp::field::cache_control, "no-store");
	boost::beast::ostream(response.body()) << "Up and running ;)";
	response.content_length(response.payload_size());
	response.set(bhttp::field::content_type, "text/plain");
        response.result(bhttp::status::ok);
}

bool HTTPSession::check_operations(bhttp::response<bhttp::dynamic_body> &response, const HttpOperations &operations) {

        std::string uri(request.target());
        std::size_t pos = uri.find("?");

        if (pos != std::string::npos)
                uri.erase(pos, uri.length() - pos);

        const auto &operation = operations.find(uri);

        if (operation != get_operations_.end()) {
                (*operation).second(response);
		return true;
	}

	return false;
}

std::string HTTPSession::generate_redirection_url(bhttp::request<bhttp::dynamic_body>& req) const {

        std::ostringstream redirect;

        redirect << "http://";

        if (boost::beast::string_view hostname = req[bhttp::field::host]; hostname.length() > 0)
                redirect << hostname;
        else {
        	boost::system::error_code ec;
        	boost::asio::ip::tcp::endpoint endpoint = socket_.local_endpoint(ec);

		if (!ec)
                	redirect << endpoint.address().to_string() << ":" << endpoint.port();
	}

        redirect << HTTPUris::show_uris;

	return redirect.str();
}

void HTTPSession::show_evidences(bhttp::response<bhttp::dynamic_body> &response) {

        if (pdis_->getEvidences() == false) {
		std::ostringstream html;

		html << "<html><body>\n"
			<< "<script>\n"
			<< "setTimeout(function() {\n"
			<< "  window.location.href = '" << generate_redirection_url(request) << "';\n"
			<< "}, " << HTTPSession::script_delay_error << ");\n"
			<< "</script>\n"
			<< "<p>Evidences not available.</p>\n"
			<< "</body></html>";

		boost::beast::ostream(response.body()) << html.str();
                response.set(bhttp::field::content_type, "text/html");
                response.content_length(response.payload_size());
                response.result(bhttp::status::not_found);
        } else {
                std::ifstream pcapfile(pdis_->getEvidencesFilename(), std::ios::binary);
                if (pcapfile.good()) {
                        int32_t total_size = pdis_->getEvidencesFileSize();
                        std::vector<uint8_t> buffer(total_size);

                        pcapfile.read((char*)&buffer[0], total_size);

			response.set(bhttp::field::cache_control, "no-store");
                        boost::beast::ostream(response.body()).write((char*)&buffer[0], total_size);
                        response.content_length(response.payload_size());
                        response.set(bhttp::field::content_type, "application/vnd.tcpdump.pcap");
                        response.result(bhttp::status::ok);
                        pcapfile.close();
                } else {
			std::ostringstream html;

			html << "<html><body>\n"
				<< "<script>\n"
				<< "setTimeout(function() {\n"
				<< "  window.location.href = '" << generate_redirection_url(request) << "';\n"
				<< "}, " << HTTPSession::script_delay_error << ");\n"
				<< "</script>\n"
				<< "<p>Evidences pcapfile not found.</p>\n"
				<< "</body></html>";

			boost::beast::ostream(response.body()) << html.str();
                        response.set(bhttp::field::content_type, "text/html");
                        response.content_length(response.payload_size());
                        response.result(bhttp::status::not_found);
                }
        }
}

void HTTPSession::parse_arguments(const std::string &data, std::map<std::string, std::string> &arguments) const {

	std::vector<std::string> parameters;

	boost::split(parameters, data, boost::is_any_of("&"));

	for (auto &parameter: parameters) {
		std::vector<std::string> entry;

		boost::split(entry, parameter, boost::is_any_of("="));

		if ((entry.size() == 2) and (entry[1].length() > 0))
			arguments[entry[0]] = entry[1];
	}
}

/* LCOV_EXCL_STOP */

} // namespace aiengine
